﻿using System;
using System.Reflection;
using System.Collections.Generic;
using System.Text;
using Ndo;

namespace Ndo.Reflections
{
    public delegate object InvokeHandler(object[] parameters);

    public class Invoker
    {
        public Invoker(MethodInfo method)
            :this(null, method)
        {}
        public Invoker(object obj, MethodInfo method)
        {
            if (method == null)
                throw new ArgumentNullException();
            if (method.IsStatic)
            {
                if (obj != null)
                    throw new ArgumentException();
            }
            else if (obj == null)
                throw new ArgumentNullException();
            else if (obj.GetType() != method.DeclaringType)
                throw new ArgumentException();
            Obj = obj;
            Method = method;
        }

        public readonly object Obj;
        public readonly MethodInfo Method;
        public bool CanInvoke(params Type[] parameterTypes)
        {
            foreach (Pair<Type, ParameterInfo> pair in Enumerate<Type, ParameterInfo>.Bind(parameterTypes, Method.GetParameters()))
            {
                if (!pair.B.ParameterType.IsAssignableFrom(pair.A))
                    return false;
            }
            return true;
        }
        public bool CanInvoke(params object[] parameters)
        {
            foreach (Pair<object, ParameterInfo> pair in Enumerate<object, ParameterInfo>.Bind(parameters, Method.GetParameters()))
            {
                if(pair.A == null)
                {
                    if(pair.B.ParameterType.IsValueType)
                        return false;
                }
                else
                {
                    if (!pair.B.ParameterType.IsAssignableFrom(pair.A.GetType()))
                        return false;
                }
            }
            return true;
        }
        public object Invoke(params object[] parameters)
        {
            return Method.Invoke(Obj, parameters);
        }
        public static InvokeHandler CreateHandler(object obj, MethodInfo method)
        {
            return new Invoker(obj, method).Invoke;
        }
        public static InvokeHandler CreateHandler(MethodInfo method)
        {
            return new Invoker(method).Invoke;
        }

        public static implicit operator Invoker(MethodInfo mi)
        {
            return new Invoker(mi);
        }
    }
}
